<?php

namespace app\common\controller;

use think\Cache;
use think\Config;
use think\Controller;
use think\exception\HttpResponseException;
use think\Log;
use think\Response;

class BaseController extends Controller
{
    /**
     * 签名key.
     *
     * @var string
     */
    protected $signKey = '';

    /**
     * 来源类型.
     *
     * @var int
     */
    protected $type;

    /**
     * 媒体id.
     *
     * @var int
     */
    protected $mpId;

    /**
     * @var string
     */
    protected $token;

    /**
     * 参数.
     *
     * @var mixed
     */
    protected $params;

    /**
     * 用户信息(登录后有).
     *
     * @var array
     */
    protected $memberInfo = [];

    /**
     * 当前模块.
     *
     * @var
     */
    protected $module;

    /**
     * 默认日志类型.
     *
     * @var string
     */
    protected $logType = 'notice';

    /**
     * 初始化.
     */
    protected function _initialize()
    {
        if (Config::has('sign_key')) {
            $this->signKey = Config::get('sign_key');
        }
        $this->module = $this->request->module();
        if ($this->request->isPost()) {
            $params = $this->request->post();
        } elseif ($this->request->isGet()) {
            $params = $this->request->get();
        } else {
            $params = $this->request->param();
        }
        $this->params = $params;
    }

    /**
     * 参数加签.
     *
     * @param array $data 接口参数
     *
     * @return string
     */
    protected function decodeSign($data)
    {
        $data = array_change_key_case($data);
        ksort($data);
        foreach ($data as &$val) {
            if (\is_array($val)) {
                $val = json_encode($val, JSON_UNESCAPED_UNICODE);
            }
        }
        $data = $this->buildQuery($data);

        return  strtoupper(md5($data.'&key='.$this->signKey));
    }

    /**
     * 校验签名.
     *
     * @return mixed
     */
    protected function verifySign()
    {
        $data = $this->params;
        // 如果不需要传参数 直接返回true
        if (empty($data)) {
            return true;
        }
        $sign = isset($data['sign']) ? $data['sign'] : false;
        if ($sign) {
            unset($data['sign']);
        }
        $cachePrefix = 'API:SIGN:'.$this->type.':';
        // 必须传随机字符串且两分钟内不能重复使用 缓存签名sign 防止重复使用
        if (!isset($data['nonceStr']) || $sign !== $this->decodeSign($data) || Cache::has($cachePrefix.$sign)) {
            // 记录一下错误日志
            Log::record('Sign Error :'.json_encode($data, JSON_UNESCAPED_UNICODE), 'notice');
            $this->fail('SIGN_ERROR');
        }
        Cache::set($cachePrefix.$sign, json_encode($data), 120);

        return true;
    }

    /**
     * 拼装签名前的字符串.
     *
     * @param $data
     *
     * @return string
     */
    protected function buildQuery($data)
    {
        $str = '';
        if (\is_array($data)) {
            foreach ($data as $key => $value) {
                if (\is_array($value)) {
                    $str .= $key.'='.json_encode($value).'&';
                } else {
                    $str .= $key.'='.$value.'&';
                }
            }
            $str = mb_substr($str, 0, -1);
        } else {
            $str = $data;
        }

        return $str;
    }

    /**
     * 操作成功跳转的快捷方法.
     *
     * @param mixed $msg    提示信息
     * @param mixed $data   返回的数据
     * @param int   $code   返回码
     * @param array $header 发送的Header信息
     */
    protected function win($msg = '', $data = '', $code = 1, array $header = [])
    {
        if (\is_array($msg)) {
            $code = $msg['code'];
            $data = $msg['data'];
            $msg = $msg['msg'];
        }
        $result = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];

        $type = $this->getResponseType();
        $header['Access-Control-Allow-Origin'] = '*';
        $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,X-Token,X-Bid,X-MpId';
        $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS';
        $response = Response::create($result, $type)->header($header);

        throw new HttpResponseException($response);
    }

    /**
     * 操作错误跳转的快捷方法.
     *
     * @param mixed $msg    提示信息,若要指定错误码,可以传数组,格式为['code'=>您的错误码,'msg'=>'您的错误消息']
     * @param mixed $data   返回的数据
     * @param array $header 发送的Header信息
     */
    protected function fail($msg = '', $data = '', array $header = [])
    {
        $code = 0;
        if (\is_array($msg)) {
            $code = $msg['code'];
            $msg = $msg['msg'];
        } elseif (\is_string($msg) && Config::has('error.'.$msg)) {
            $error = Config::get('error.'.$msg);
            $msg = $error['msg'];
            $code = $error['code'];
        }

        $result = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];

        $type = $this->getResponseType();
        $header['Access-Control-Allow-Origin'] = '*';
        $header['Access-Control-Allow-Headers'] = 'X-Requested-With,Content-Type,X-Token,X-Bid,X-MpId';
        $header['Access-Control-Allow-Methods'] = 'GET,POST,PATCH,PUT,DELETE,OPTIONS';
        $response = Response::create($result, $type)->header($header);

        throw new HttpResponseException($response);
    }

    /**
     * 机构端小程序使用
     * 判断请求头中是否包含token(一般用于前置检查:$beforeActionList).
     *
     * @return bool
     */
    protected function hasEduToken()
    {
        if (!$this->token = $this->request->header('X-Token')) {
            $this->fail('NOT_LOGIN');
        }
        $this->memberInfo = Cache::get('EDU_TOKEN:'.$this->type.':'.$this->token);

        if (empty($this->memberInfo)) {
            $this->fail('TOKEN_EXPIRED');
        }

        $this->type = $this->memberInfo['type'];

        return true;
    }

	/**
	 * 家长端小程序使用
	 * 判断请求头中是否包含token(一般用于前置检查:$beforeActionList).
	 *
	 * @return bool
	 */
    protected function hasParentToken()
    {
        if (!$this->token = $this->request->header('X-Token')) {
            $this->fail('NOT_LOGIN');
        }
        $this->memberInfo = Cache::get('PARENT_TOKEN:'.$this->token);

        if (empty($this->memberInfo)) {
            $this->fail('TOKEN_EXPIRED');
        }

        return true;
    }

    /**
     * 判断请求头中是否包含type(一般用于前置检查:$beforeActionList).
     * ps: 区分来源.
     *
     * @return bool
     */
    protected function hasType()
    {
        if (!$this->type = $this->request->header('X-Type')) {
            $this->fail('PARAM_ERROR');
        }

        return true;
    }
}
